/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) TWChinaID.java 2018-08-05 23:31
 */

package cn.jh.common.core.id;

import cn.jh.common.core.domain.Gender;
import com.google.common.collect.ImmutableMap;
import org.inferred.freebuilder.FreeBuilder;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import java.util.Map;
import java.util.regex.Pattern;


/**
 * 中国台湾身份证号码信息。
 *
 * @author Fuchun
 * @since 1.0
 */
@FreeBuilder
abstract class TWChinaID extends ChinaID {

    static final String TW_STATE_CODE = "71";
    private static final int TW_ID_LENGTH = 10;

    /** 台湾身份证格式 */
    private static final Pattern TW_ID_PATTERN = Pattern.compile("^[A-Z][12][0-9]{8}$");

    /**
     * 台湾身份首字母对应的数字码和城市表。
     */
    private static final ImmutableMap<String, Map.Entry<Integer, String>> TW_CODE_TABLE;

    private static final long serialVersionUID = 5841985339674191880L;

    static {
        ImmutableMap.Builder<String, Map.Entry<Integer, String>> builder = ImmutableMap.builder();
        builder.put("A", CodeEntry.of(10, "台北市"));
        builder.put("B", CodeEntry.of(11, "台中市"));
        builder.put("C", CodeEntry.of(12, "基隆市"));
        builder.put("D", CodeEntry.of(13, "台南市"));
        builder.put("E", CodeEntry.of(14, "高雄市"));
        builder.put("F", CodeEntry.of(15, "台北县"));
        builder.put("G", CodeEntry.of(16, "宜兰县"));
        builder.put("H", CodeEntry.of(17, "桃园县"));
        builder.put("J", CodeEntry.of(18, "新竹县"));
        builder.put("K", CodeEntry.of(19, "苗栗县"));
        builder.put("L", CodeEntry.of(20, "台中县"));
        builder.put("M", CodeEntry.of(21, "南投县"));
        builder.put("N", CodeEntry.of(22, "彰化县"));
        builder.put("P", CodeEntry.of(23, "云林县"));
        builder.put("Q", CodeEntry.of(24, "嘉义县"));
        builder.put("R", CodeEntry.of(25, "台南县"));
        builder.put("S", CodeEntry.of(26, "高雄县"));
        builder.put("T", CodeEntry.of(27, "屏东县"));
        builder.put("U", CodeEntry.of(28, "花莲县"));
        builder.put("V", CodeEntry.of(29, "台东县"));
        builder.put("W", CodeEntry.of(30, "金门县"));
        builder.put("X", CodeEntry.of(31, "澎湖县"));
        builder.put("Y", CodeEntry.of(32, "阳明山"));
        builder.put("Z", CodeEntry.of(33, "连江县"));
        builder.put("I", CodeEntry.of(34, "嘉义市"));
        builder.put("O", CodeEntry.of(35, "新竹市"));

        TW_CODE_TABLE = builder.build();
    }

    static Map.Entry<Integer, String> getCodeEntry(String f) {
        return TW_CODE_TABLE.getOrDefault(f, CodeEntry.empty());
    }

    public static TWChinaID parse(String twId) {
        if (twId == null || twId.length() != TW_ID_LENGTH ||
                !TW_ID_PATTERN.matcher(twId).matches()) {
            return new Builder(twId)
                    .setValid(false).build();
        }
        String f = twId.substring(0, 1);
        Map.Entry<Integer, String> fp = getCodeEntry(f);
        if (!validTwIdLastCode(twId, fp.getKey())) {
            return new Builder(twId)
                    .setValid(false).build();
        }
        char g = twId.charAt(1);
        Gender gender = g == '1' ? Gender.MALE : Gender.FEMALE;
        return new Builder(twId)
                .setValid(true)
                .setAreaCode(f)
                .setGender(gender)
                .build();
    }

    /**
     * 检查给定的输入是否是一个有效的台湾身份证号。
     *
     * @param twId 要检查的身份证号。
     * @return 如果给定的输入是一个有效的台湾身份证号，则返回{@code true}，否则{@code false}。
     */
    @Contract("null -> false")
    public static boolean validId(String twId) {
        if (twId == null || twId.length() != TW_ID_LENGTH) {
            return false;
        }
        if (!TW_ID_PATTERN.matcher(twId).matches()) {
            return false;
        }
        String f = twId.substring(0, 1);
        Map.Entry<Integer, String> fp = getCodeEntry(f);
        return validTwIdLastCode(twId, fp.getKey());
    }

    /**
     * 检查给定的台湾身份证号的最后一位校验码是否有效。
     *
     * @param id 要检查的身份证号。
     * @param firstCode 首位英文字符对应的编号。
     * @return 如果给定的台湾身份证号的最后一位校验码有效，则返回{@code true}，否则{@code false}。
     */
    static boolean validTwIdLastCode(String id, int firstCode) {
        int x1 = firstCode / 10, x2 = firstCode % 10;
        int sum = x1 + x2 * 9, c;
        for (int i = 1; i < id.length() - 1; i++) {
            c = (id.charAt(i) - '0') * (9 - i);
            sum += c;
        }
        int last = id.charAt(id.length() - 1) - '0';
        sum += last;
        return sum % 10 == 0;
    }

    static class Builder extends TWChinaID_Builder {

        Builder(String id) {
            setNumber(id);
            setAge(-1);
        }
    }

    private static class CodeEntry implements Map.Entry<Integer, String> {

        private static final CodeEntry EMPTY = new CodeEntry(0, null);

        @Contract(pure = true)
        static CodeEntry empty() {
            return EMPTY;
        }

        @NotNull
        static CodeEntry of(Integer key, String value) {
            return new CodeEntry(key, value);
        }

        private final Integer key;
        private final String value;

        private CodeEntry(Integer key, String value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public Integer getKey() {
            return key;
        }

        @Override
        public String getValue() {
            return value;
        }

        @Override
        public final String setValue(String value) {
            throw new UnsupportedOperationException();
        }
    }

    @Contract(pure = true)
    @Override
    public final String getStateCode() {
        return TW_STATE_CODE;
    }

    @Contract(pure = true)
    @Override
    public final int getValidLength() {
        return TW_ID_LENGTH;
    }
}
